class ShaderProgram {
protected:

	GLint program;

public:

	class Shader{
	
	friend class ShaderProgram;
	
	protected:

		GLint shader;

	public:
	
		bool compile_status(){
			GLint compile_status;
			glGetShaderiv(shader,GL_COMPILE_STATUS,&compile_status);
			return compile_status;
		}
		
		const string error_string() const{
			string result;
			GLint info_log_length;
			glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&info_log_length);
			result.resize(info_log_length);
			glGetShaderInfoLog(shader,info_log_length,0,&result[0]);
			return result;
		}

		bool load_file(const char* name){
			bool result = false;
			ifstream file(name,ifstream::binary | ifstream::in | ifstream::ate);
			if (file.is_open()){
				unsigned int size = file.tellg();
				file.seekg(0,file.beg);
				char* source = new char[size+1];
				file.read(source,size);
				source[size]=0;
				result = load_source(source);
				delete[] source;
				file.close();
			}
			return result;
		}
		
		bool load_source(const char* src){
			glShaderSource(shader,1,&src,0);
			glCompileShader(shader);
			return compile_status();
		}
		
		Shader(GLenum type){
			shader = glCreateShader(type);
		}
		
		~Shader(){
			glDeleteShader(shader);
		}

	};
	
	Shader vertex_shader;
	
	Shader fragment_shader;
	
	bool link_status() const{
		GLint link_status;
		glGetProgramiv(program,GL_LINK_STATUS,&link_status);
		return link_status;
	}
	
	const string error_string() const{
		string result;
		GLint info_log_length;
		glGetProgramiv(program,GL_INFO_LOG_LENGTH,&info_log_length);
		result.resize(info_log_length);
		glGetProgramInfoLog(program,info_log_length,0,&result[0]);
		return result;
	}
	
	bool link(){
		glDetachShader(program,vertex_shader.shader);
		glDetachShader(program,fragment_shader.shader);
		if (vertex_shader.compile_status())
			glAttachShader(program,vertex_shader.shader);
		if (fragment_shader.compile_status())
			glAttachShader(program,fragment_shader.shader);
		glLinkProgram(program);
		return link_status();
	}
	
	void use(){
		active_program = program;
		glUseProgram(program);
	}
	
	GLint get_uniform_location(const char* name){
		return glGetUniformLocation(program,name);
	}
	
	void uniform_matrix(GLint location, const mat4& matrix){
		glUniformMatrix4fv(location,1,GL_FALSE,value_ptr(matrix));
	}
	
	void uniform_vec4(GLint location, const vec4& v){
		glUniform4fv(location,1,value_ptr(v));
	}
	
	ShaderProgram():
		vertex_shader(GL_VERTEX_SHADER),
		fragment_shader(GL_FRAGMENT_SHADER)
	{
		program = glCreateProgram();
	}
	
	~ShaderProgram(){
		glDeleteProgram(program);
	}
	
	static GLint active_program;
};

GLint ShaderProgram::active_program = 0;